//
//  KsRange.swift
//  iKinds
//
//  Created by kinds on 2016/12/4.
//  Copyright © 2016年 Razeware LLC. All rights reserved.
//

import Foundation

class Regex{
    let internalExp:NSRegularExpression
    let pattern:String
    
    func firstStrings(_ input:String)->[String]?{
        if let match = first(input){
            var results = [String]()
            
            for i in 0..<match.numberOfRanges{
                let range = match.rangeAt(i)
                if range.location != NSNotFound{
                    results.append((input as NSString).substring(with: range))
                }
            }
            return results
        }
        return nil
    }
    
    func first(_ input:String)->NSTextCheckingResult?{
        let match = internalExp.firstMatch(in: input, options: .init(rawValue: 0), range: NSRange(location: 0, length: input.characters.count))
        return match
    }
    
    func match(_ input:String)->[NSTextCheckingResult]{
        let matches = internalExp.matches(in: input, options: .init(rawValue: 0), range: NSRange(location: 0, length: input.characters.count))
        return matches
    }
    
    func test(_ input:String)->Bool{
        let matches = match(input)
        return matches.count > 0
    }
    
    init(_ pattern:String) throws {
        self.pattern = pattern
        internalExp = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
    }
}

/*
	range的解析说明:(假设当天为2016.11.11日 2030)
	11或11-	: 2016.11.11 2030-11.30 2400
	10或10-		: 格式错误
	11.15	: 11.15 0000 - 11.15 2400
	10.1	: 格式错误
	11-12	: 11.11 2030 - 12.31 2400
	10-12	: 格式错误
	11.12-12.11	: 11.12-12.11
	11.5-12.11	: 格式错误
	11.15-		: 11.15 0000 - 11.30 2400
	11.03-		: 格式错误
	2017.10.10 : 2017.10.10 0000 - 2400
	2017.11	:	2017年11月全月
	2017.05-07或2017.05-2017.07 : 2017.05.01-2017.07.31
	2017.01.02 - 2017.07.11 : 2017.01.02至2017.07.11
	2017	: 2017年全年,即1.1 0000 -12.31 2400
	2015	:格式错误
	PS:
	1.所有准确时间点为:
	a.如果起始点为今天则为即时,否则为当日00:00
	b.结束日的24:00
	2.起始点不能为过去时间
 */

class KsRangeQuery{
	private static let b = "(?:\\s+)?"
	private static let m = "((?:0)?[1-9]|10|11|12)"
	private static let d = "((?:0)?[1-9]|1[0-9]|2[0-9]|3[0-2])"
	private static let y = "(2[0-9]{3})"
	private static let h = "(2[0-3]|1[0-9]|(?:0)[0-9])"
	private static let M = "([1-5][0-9]|0[0-9])"
	private static let tp = "\(h)\(M)\(b)-\(b)\(h)\(M)"	//time pair
	//11,11-,11-12
	let ft0 = "^\(b)\(m)\(b)(?:-|-\(b)\(m))?\(b)$"
	//2017,2017.10,2017.10.11
	let ft1 = "^\(b)(?:\(y)|\(y)[.]\(m)|\(y)[.]\(m)[.]\(d))\(b)$"
	//11.11,11.11-,11.11-12,11.11-12.11
	let ft2 = "^\(b)\(m)[.]\(d)\(b)(-|-\(b)\(m)|-\(b)\(m)[.]\(d))?\(b)$"
	//2017.10-,2017.01-03,2017.01-2017.03
	let ft3 = "^\(b)\(y)[.]\(m)\(b)(?:-|-\(b)\(m)|-\(b)\(y)[.]\(m))?\(b)$"
	//2017.01.05-,2017.01.05-07,2017.01.05-07.11,2017.01.05-2017.09.11
	let ft4 = "^\(b)\(y)[.]\(m)[.]\(d)\(b)-\(b)(?:\(m)|\(m)[.]\(d)|\(y)[.]\(m)[.]\(d))?\(b)$"
    //2017.01.05-2018.02
    let ft5 = "^\(b)\(y)[.]\(m)[.]\(d)\(b)-\(b)\(y)[.]\(m)\(b)$"
	//2017.10.10 0800-2300,10.10 0800-2200,10 0800-2215,0800-2215,-2215
	let ft6 = "^\(b)(?:(?:\(y)[.])?\(m)[.]\(d)\\s\(tp)|(?:\(d)\\s)?\(tp)|-\(b)\(h)\(M))\(b)$"
    
    private(set) var string:String
    private let ft0exp:Regex
    private let ft1exp:Regex
    private let ft2exp:Regex
    private let ft3exp:Regex
    private let ft4exp:Regex
    private let ft5exp:Regex
	private let ft6exp:Regex
    
    init?(string:String) {
        self.string = string
        do{
            ft0exp = try Regex(ft0)
            ft1exp = try Regex(ft1)
            ft2exp = try Regex(ft2)
            ft3exp = try Regex(ft3)
            ft4exp = try Regex(ft4)
            ft5exp = try Regex(ft5)
			ft6exp = try Regex(ft6)
        }catch{
            return nil
        }
    }
	
	private func lastDayIn(year:Int,month:Int)->Int?{
		var year = year,month = month
		let df = DateFormatter()
		df.dateFormat = "yyyy/MM/dd"
		if month == 12{
			month = 0
			year += 1
		}
		if let d = df.date(from: String(format: "%04d/%02d/01", year,month+1)){
			let endDay = Date(timeInterval: -1*24*60*60, since: d)
			let str = df.string(from: endDay)
			return Int((str as NSString).lastPathComponent)
		}
		return nil
	}
    
    private func makeFullDate(year:Int,month:Int,day:Int,hour:Int,minute:Int,second:Int)->Date?{
        guard day <= lastDayIn(year: year, month: month)! else {return nil}
        var compt = DateComponents()
        compt.calendar = .current
        compt.timeZone = .current
        compt.year = year
        compt.month = month
        compt.day = day
        compt.hour = hour
        compt.minute = minute
        compt.second = second
        return Calendar.current.date(from: compt)
    }
	
	private func makeStartDate(year:Int,month:Int,day:Int,isFromNewDay:Bool)->Date?{
        guard day <= lastDayIn(year: year, month: month)! else {return nil}
		var compt = DateComponents()
		compt.calendar = Calendar.current
		compt.timeZone = TimeZone.current
		compt.year = year
		compt.month = month
		compt.day = day
		if isFromNewDay{
			compt.hour = 0
			compt.minute = 0
			compt.second = 0
		}else{
			let now = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
			compt.hour = now.hour
			compt.minute = now.minute
			compt.second = now.second
		}
		return Calendar.current.date(from: compt)!
	}
    
    
	
	private func makeEndDate(year:Int,month:Int,day:Int)->Date?{
        guard day <= lastDayIn(year: year, month: month)! else {return nil}
		var compt = DateComponents()
		compt.calendar = Calendar.current
		compt.timeZone = TimeZone.current
		compt.year = year
		compt.month = month
		compt.day = day
		compt.hour = 24
		compt.minute = 00
		compt.second = 00
		return Calendar.current.date(from: compt)!
	}
	
	func match()->(Date,Date)?{
		if let r = match_ft0(){
			return r
		}else if let r = match_ft1(){
			return r
		}else if let r = match_ft2(){
			return r
        }else if let r = match_ft3(){
            return r
        }else if let r = match_ft4(){
            return r
        }else if let r = match_ft5(){
            return r
		}else if let r = match_ft6(){
			return r
		}
		return nil
	}
	
	//分钟,小时,天,月
	func recommendTimeUnit(start:Date,end:Date)->KsMain.timeUnit?{
		var result = Calendar.current.dateComponents([Calendar.Component.minute], from: start, to: end)
		if let minute = result.minute{
			if minute <= 60 {
				return .perMinute
			}else if minute <= 60 * 24{
				return .perHour
			}else if minute <= 60 * 24 * 32{
				return .perDay
			}else{
				return .perMonth
			}
		}else{
			return nil
		}
	}
	
	//2017.10.10 0800-2300,10.10 0800-2200,10 0800-2215,0800-2215,-2215
	private func match_ft6()->(Date,Date)?{
		guard var ary = ft6exp.firstStrings(string),ary.count != 0 else {return nil}
		
		ary.reverse()
		ary.removeLast()
		var h1,minute1:Int
		h1 = Int(ary[1])!
		minute1 = Int(ary[0])!
		var y,m,d,h0,minute0:Int?
		
		for i in 2..<7{
			if ary[safe:i] != nil{
				switch i{
				case 2:minute0 = Int(ary[2])!
				case 3:h0 = Int(ary[3])!
				case 4:d = Int(ary[4])!
				case 5:m = Int(ary[5])!
				case 6:y = Int(ary[6])!
				default:break
				}
			}else{
				break
			}
		}
		
		let compt = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
		let year = compt.year!,month = compt.month!,day = compt.day!,hour = compt.hour!,minute = compt.minute!,second = compt.second!
		
		if let h0 = h0,let minute0 = minute0{
			if h0 < hour {return nil}
			if h0 > h1 {return nil}
			if h0 == hour,minute0 <= minute {return nil}
			if h0 == h1,minute0 >= minute1 {return nil}
		}else{
			if h1 < hour {return nil}
			if h1 == hour,minute1 <= minute {return nil}
		}
		
		if let y = y{
			if y < year {return nil}
			if y == year,m! < month {return nil}
			if y == year,m! == month,d! < day {return nil}
		}else{
			if let m = m{
				if m < month {return nil}
				if m == month,d! < day {return nil}
			}else{
				if let d = d{
					if d < day {return nil}
				}
			}
		}
		
		var used_y,used_m,used_d,start_h,end_h,start_minute,end_minute:Int
		var start_s = 0,end_s = 0
        
		
		if let y = y{
			used_y = y
			used_m = m!
			used_d = d!
			start_h = h0!
			end_h = h1
			start_minute = minute0!
			end_minute = minute1
		}else if let m = m{
			used_y = year
			used_m = m
			used_d = d!
			start_h = h0!
			end_h = h1
			start_minute = minute0!
			end_minute = minute1
		}else if let d = d{
			used_y = year
			used_m = month
			used_d = d
			start_h = h0!
			end_h = h1
			start_minute = minute0!
			end_minute = minute1
		}else if let h0 = h0,let minute0 = minute0{
			used_y = year
			used_m = month
			used_d = day
			start_h = h0
			start_minute = minute0
			end_h = h1
			end_minute = minute1
        }else{
            used_y = year
            used_m = month
            used_d = day
            start_h = hour
            start_minute = minute
            end_h = h1
            end_minute = minute1
            start_s = second
        }
		
		let start = makeFullDate(year: used_y, month: used_m, day: used_d, hour: start_h, minute: start_minute, second: start_s)
        let end = makeFullDate(year: used_y, month: used_m, day: used_d, hour: end_h, minute: end_minute, second: end_s)
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
	}
    
    //2017.01.05-2018.02
    private func match_ft5()->(Date,Date)?{
        guard let ary = ft5exp.firstStrings(string),ary.count != 0 else {return nil}
        var y0,m0,d0,y1,m1:Int
        y0 = Int(ary[1])!
        m0 = Int(ary[2])!
        d0 = Int(ary[3])!
        y1 = Int(ary[4])!
        m1 = Int(ary[5])!
        
        let compt = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
        let year = compt.year!,month = compt.month!,day = compt.day!
        
        if y0 < year {return nil}
        if y0 == year,m0 < month {return nil}
        if y0 == year,m0 == month,d0 < day {return nil}
        if y1 < y0 {return nil}
        if y1 == y0,m1 < m0 {return nil}
        
        var start_y,end_y,start_m,end_m,start_d,end_d:Int
        var isNewDay = true
        
        if y0 == year,m0 == month,d0 == day {isNewDay = false}
        start_y = y0
        end_y = y1
        start_m = m0
        end_m = m1
        start_d = d0
        end_d = lastDayIn(year: y1, month: m1)!
        
        let start = makeStartDate(year: start_y, month: start_m, day: start_d, isFromNewDay: isNewDay)
        let end = makeEndDate(year: end_y, month: end_m, day: end_d)
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
    }
    
    //2017.01.05-,2017.01.05-07,2017.01.05-07.11,2017.01.05-2017.09.11
    private func match_ft4()->(Date,Date)?{
        guard let ary = ft4exp.firstStrings(string),ary.count != 0 else {return nil}
        var y0,m0,d0:Int
        var y1,m1,d1:Int?
        y0 = Int(ary[1])!
        m0 = Int(ary[2])!
        d0 = Int(ary[3])!
        for i in 4..<7{
            if ary[safe:i] != nil{
                switch i{
                case 5:
                    d1 = Int(ary[5])!; fallthrough
                case 4:
                    m1 = Int(ary[4])!
                case 6:
                    y1 = Int(ary[4])!
                    m1 = Int(ary[5])!
                    d1 = Int(ary[6])!
                default:
                    break
                }
            }else{
                break
            }
        }
        
        let compt = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
        let year = compt.year!,month = compt.month!,day = compt.day!
        
        if y0 < year {return nil}
        if y0 == year,m0 < month {return nil}
        if y0 == year,m0 == month,d0 < day {return nil}
        if let y1 = y1,y1 < y0 {return nil}
        if let y1 = y1,let m1 = m1,y1 == y0,m1 < m0 {return nil}
        if let y1 = y1,let m1 = m1,let d1 = d1,y1 == y0,m1 == m0,d1 < d0 {return nil}
        if y1 == nil,let m1 = m1,m1 < m0 {return nil}
        if y1 == nil,let m1 = m1,let d1 = d1,m1 == m0,d1 < d0 {return nil}
        
        var start_y,end_y,start_m,end_m,start_d,end_d:Int
        var isNewDay = true
        if y0 == year,m0 == month,d0 == day{
            isNewDay = false
        }
        if let y1 = y1{
            start_y = y0
            end_y = y1
            start_m = m0
            end_m = m1!
            start_d = d0
            end_d = d1!
        }else if let d1 = d1{
            start_y = y0
            end_y = y0
            start_m = m0
            end_m = m1!
            start_d = d0
            end_d = d1
        }else if let m1 = m1{
            start_y = y0
            end_y = y0
            start_m = m0
            end_m = m1
            start_d = d0
            end_d = lastDayIn(year: y0, month: m1)!
        }else{
            start_y = y0
            end_y = y0
            start_m = m0
            end_m = m0
            start_d = d0
            end_d = lastDayIn(year: y0, month: m0)!
        }
        
        let start = makeStartDate(year: start_y, month: start_m, day: start_d, isFromNewDay: isNewDay)
        let end = makeEndDate(year: end_y, month: end_m, day: end_d)
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
        
        
    }
	
	//2017.10-,2017.01-03,2017.01-2017.03
	private func match_ft3()->(Date,Date)?{
		let tmp = ft3exp.firstStrings(string)
		guard let ary = tmp,ary.count != 0 else {return nil}
		
		var y0,m0:Int
		var y1,m1:Int?
		y0 = Int(ary[1])!
		m0 = Int(ary[2])!
		for i in 3..<5{
			if ary[safe:i] != nil{
				switch i{
				case 3:
					m1 = Int(ary[i])!
				case 4:
                    y1 = Int(ary[3])!
					m1 = Int(ary[i])!
				default:
					break
				}
			}else{
				break
			}
		}
		
		let compt = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
		let year = compt.year!,month = compt.month!,day = compt.day!
		
        if y0 < year {return nil}
        if y0 == year{
            if m0 < month {return nil}
        }
        
        if let y1 = y1,y1 < y0 {return nil}
        if let y1 = y1,y1 == y0{
            if let m1 = m1,m1 < m0 {return nil}
        }
        if y1 == nil,let m1 = m1{
            if m1 < m0 {return nil}
        }
        
        var start_y,end_y,start_m,start_d,end_m,end_d:Int
        var isNewDay = true
        if let y1 = y1{
            if y0 == year,m0 == month{
                isNewDay = false
                start_d = day
            }else{
                start_d = 1
            }
            start_y = y0
            start_m = m0
            end_y = y1
            end_m = m1!
            end_d = lastDayIn(year: y1, month: m1!)!
        }else if let m1 = m1{
            if y0 == year,m0 == month{
                isNewDay = false
                start_d = day
            }else{
                start_d = 1
            }
            start_y = y0
            end_y = y0
            start_m = m0
            end_m = m1
            end_d = lastDayIn(year: y0, month: m1)!
        }else{
            if y0 == year,m0 == month{
                isNewDay = false
                start_d = day
            }else{
                start_d = 1
            }
            start_y = y0
            end_y = y0
            start_m = m0
            end_m = m0
            end_d = lastDayIn(year: y0, month: m0)!
        }
        
        let start = makeStartDate(year: start_y, month: start_m, day: start_d, isFromNewDay: isNewDay)
        let end = makeEndDate(year: end_y, month: end_m, day: end_d)
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
	}
	
	//11.11,11.11-,11.11-12,11.11-12.11
	private func match_ft2()->(Date,Date)?{
		let tmp = ft2exp.firstStrings(string)
		guard let ary = tmp,ary.count != 0 else {return nil}
		
		var m0,d0:Int
		var hasCross = false
		m0 = Int(ary[1])!
		d0 = Int(ary[2])!
		var m1,d1:Int?
		for i in 3..<6{
			if ary[safe:i] != nil{
				switch i{
				case 3:
					hasCross = true
				case 4:
					m1 = Int(ary[i])!
				case 5:
					d1 = Int(ary[i])!
				default:
					break
				}
			}else{
				break
			}
		}
		
		let compt = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
		let year = compt.year!,month = compt.month!,day = compt.day!
		
		if m0 < month {return nil}
		if m0 == month , d0 < day {return nil}
		if let m1 = m1 , m1 < m0 {return nil}
		if let d1 = d1 , m1! == m0 {
			if d1 < d0 {return nil}
		}
		
		var isNewDay = true
		var start_m,end_m,start_d,end_d:Int
		if let d1 = d1{
			let m1 = m1!
			if m1 == month{
				if d0 == day {
					isNewDay = false
				}
			}else{
				if m0 == month{
					if d0 == day{
						isNewDay = false
					}
				}
			}
			start_m = m0
			end_m = m1
			start_d = d0
			end_d = d1
		}else if let m1 = m1{
			if m1 == month{
				end_d = d0
				if d0 == day{
					isNewDay = false
				}
			}else{
				if m0 == month{
					if d0 == day{
						isNewDay = false
					}
				}
				end_d = lastDayIn(year: year, month: m1)!
			}
			start_m = m0
			end_m = m1
			start_d = d0
		}else if hasCross{
			if m0 == month{
				if d0 == day{
					isNewDay = false
				}
			}
			start_m = m0
			end_m = m0
			start_d = d0
			end_d = lastDayIn(year: year, month: m0)!
		}else{
			if m0 == month{
				if d0 == day{
					isNewDay = false
				}
			}
			start_m = m0
			end_m = m0
			start_d = d0
			end_d = d0
		}
		
		let start = makeStartDate(year: year, month: start_m, day: start_d, isFromNewDay: isNewDay)
		let end = makeEndDate(year: year, month: end_m, day: end_d)
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
	}
	
	//2017,2017.10,2017.10.11
	private func match_ft1()->(Date,Date)?{
		let tmp = ft1exp.firstStrings(string)
		guard let ary = tmp,ary.count != 0 else {return nil}
		
		let y = Int(ary[1])!
		var m,d:Int?
		if ary.count == 3{
			m = Int(ary[2])
		}
		if ary.count == 4{
			m = Int(ary[2])
			d = Int(ary[3])
		}
		
		let compt = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
		let year = compt.year!,month = compt.month!,day = compt.day!
		
		if y < year {return nil}
		if y == year{
			if let m = m,m < month{
				return nil
			}else if let m = m,m == month{
				if let d = d,d < day{
					return nil
				}
			}
		}
		
		let start_m,end_m,start_d,end_d:Int?
		var isNewDay = true
		if let m = m{
			start_m = m
			end_m = m
			if m == month{
				if let d = d{
					if d == day{
						isNewDay = false
					}
					start_d = d
					end_d = d
				}else{
					start_d = day
					isNewDay = false
					end_d = lastDayIn(year: y, month: m)
				}
			}else{
				if let d = d{
					start_d = d
				}else{
					start_d = 1
				}
				end_d = lastDayIn(year: y, month: m)
			}
		}else{
			if y == year{
				start_m = month
				end_m = 12
				start_d = day
				isNewDay = false
			}else{
				start_m = 1
				end_m = 12
				start_d = 1
			}
			end_d = lastDayIn(year: year, month: 12)
		}
		
		let start = makeStartDate(year: y, month: start_m!, day: start_d!, isFromNewDay: isNewDay)
		let end = makeEndDate(year: y, month: end_m!, day: end_d!)
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
	}
	
	//11,11-,11-12
    private func match_ft0()->(Date,Date)?{
        let tmp = ft0exp.firstStrings(string)
        guard let ary = tmp, ary.count != 0 else {return nil}
        
        var m0:Int
        var m1:Int?
        m0 = Int(ary[1])!
        if ary.count == 3{
            m1 = Int(ary[2])!
        }
		
        let components = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
		let year = components.year!
        
        //FIXME:如果当前为2016.12则01,01-,12-01都会视为非法
        if m0 < components.month! {
            return nil
        }else if let m1 = m1, m0 >= m1{
            return nil
        }
		
		let start_day:Int
		let end_day:Int?
		var isNewDay = false
		if m0 == components.month!{
			start_day = components.day!
		}else{
			start_day = 1
			isNewDay = true
		}
		let start = makeStartDate(year: year, month: m0, day: start_day, isFromNewDay: isNewDay)
		let end:Date?
		
		if m1 != nil{
			end_day = lastDayIn(year: year, month: m1!)
			end = makeEndDate(year: year, month: m1!, day: end_day!)
		}else{
			end_day = lastDayIn(year: year, month: m0)
			end = makeEndDate(year: year, month: m0, day: end_day!)
		}
		
		if end_day == nil{
			return nil
		}
		
        if let start = start,let end = end{
            return (start,end)
        }else{
            return nil
        }
    }
}
